
/*
 * @Author: Jerrykuku https://github.com/jerrykuku
 * @Date: 2021-1-8
 * @Version: v0.0.2
 * @thanks: FanchangWang https://github.com/FanchangWang
 */

var express = require('express');
var session = require('express-session');
var compression = require('compression');
var bodyParser = require('body-parser');
var got = require('got');
var path = require('path');
var fs = require('fs');
var { execSync, exec } = require('child_process');


var rootPath = path.resolve(__dirname, '..')
// config.sh 文件所在目录
var confFile = path.join(rootPath, 'config/config.sh');
// crontab.list 文件所在目录
var crontabFile = path.join(rootPath, 'config/crontab.list');
// config.sh 文件备份目录
var confBakDir = path.join(rootPath, 'config/bak/');
// auth.json 文件目录
var authConfigFile = path.join(rootPath, 'config/auth.json');
// sharecode.sh 文件目录
var shareCodeFile = path.join(rootPath, 'config/sharecode.sh');
// diy.sh 文件目录
var diyFile = path.join(rootPath, 'config/diy.sh');
// task_finish.sh 文件目录
var taskFinishFile = path.join(rootPath, 'config/task_finish.sh');
// bot.json 文件目录
var botFile = path.join(rootPath, 'config/bot.json');
// shortcut.list 文件目录
var shortCutFile = path.join(rootPath, 'config/shortcut.list');
// 日志目录
var logPath = path.join(rootPath, 'log/');

var authError = "错误的用户名密码，请重试";
var loginFaild = "请先登录!";

var configString = "config crontab sharecode diy taskfinish bot shortcut";

var s_token, cookies, guid, lsid, lstoken, okl_token, token, userCookie = ""

const ramdomString = (length = 6) => {
  var str = 'abcdefghijklmnopqrstuvwxyz';
  str += str.toUpperCase();
  str += '0123456789';
  var _str = '';
  for (let i = 0; i < length; i++) {
    var rand = Math.floor(Math.random() * str.length);
    _str += str[rand];
  }
  return _str;
}

// function randPhoneId() {
//   return Math.random().toString().slice(2, 10) +
//   Math.random().toString().slice(2, 10) +
//   '-' +
//   Math.random().toString().slice(2, 10) +
//   Math.random().toString().slice(2, 10)
// }

var jd_ua = `jdapp;android;10.1.0;10;5653336683738366-7316732666536673;network/wifi;model/Redmi Note 8 Pro;osVer/29;appBuild/89561;partner/jingdong;eufv/1;Mozilla/5.0 (Linux; Android 10; Redmi Note 8 Pro Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045230 Mobile Safari/537.36`


function praseSetCookies(response) {
    s_token = response.body.s_token
    guid = response.headers['set-cookie'][0]
    guid = guid.substring(guid.indexOf("=") + 1, guid.indexOf(";"))
    lsid = response.headers['set-cookie'][2]
    lsid = lsid.substring(lsid.indexOf("=") + 1, lsid.indexOf(";"))
    lstoken = response.headers['set-cookie'][3]
    lstoken = lstoken.substring(lstoken.indexOf("=") + 1, lstoken.indexOf(";"))
    cookies = "guid=" + guid + "; lang=chs; lsid=" + lsid + "; lstoken=" + lstoken + "; "
}

function getCookie(response) {
    var TrackerID = response.headers['set-cookie'][0]
    TrackerID = TrackerID.substring(TrackerID.indexOf("=") + 1, TrackerID.indexOf(";"))
    var pt_key = response.headers['set-cookie'][1]
    pt_key = pt_key.substring(pt_key.indexOf("=") + 1, pt_key.indexOf(";"))
    var pt_pin = response.headers['set-cookie'][2]
    pt_pin = pt_pin.substring(pt_pin.indexOf("=") + 1, pt_pin.indexOf(";"))
    var pt_token = response.headers['set-cookie'][3]
    pt_token = pt_token.substring(pt_token.indexOf("=") + 1, pt_token.indexOf(";"))
    var pwdt_id = response.headers['set-cookie'][4]
    pwdt_id = pwdt_id.substring(pwdt_id.indexOf("=") + 1, pwdt_id.indexOf(";"))
    var s_key = response.headers['set-cookie'][5]
    s_key = s_key.substring(s_key.indexOf("=") + 1, s_key.indexOf(";"))
    var s_pin = response.headers['set-cookie'][6]
    s_pin = s_pin.substring(s_pin.indexOf("=") + 1, s_pin.indexOf(";"))
    cookies = "TrackerID=" + TrackerID + "; pt_key=" + pt_key + "; pt_pin=" + pt_pin + "; pt_token=" + pt_token + "; pwdt_id=" + pwdt_id + "; s_key=" + s_key + "; s_pin=" + s_pin + "; wq_skey="
    var userCookie = "pt_key=" + pt_key + ";pt_pin=" + pt_pin + ";";
    console.log("\n############  登录成功，获取到 Cookie  #############\n\n");
    console.log('Cookie1="' + userCookie + '"\n');
    console.log("\n####################################################\n\n");
    return userCookie;
}

/**
 * 预加载 Cookie 相关数据
 */
async function step1() {
    try {
        s_token, cookies, guid, lsid, lstoken, okl_token, token = ""
        let timeStamp = (new Date()).getTime()
        let url = 'https://plogin.m.jd.com/cgi-bin/mm/new_login_entrance?lang=chs&appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=' + timeStamp + '&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport'
        const response = await got(url, {
            responseType: 'json',
            headers: {
                'Connection': 'Keep-Alive',
                'Content-Type': 'application/x-www-form-urlencoded',
                'Accept': 'application/json, text/plain, */*',
                'Accept-Language': 'zh-cn',
                'Referer': 'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wq.jd.com/passport/LoginRedirect?state=' + timeStamp + '&returnurl=https://home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport',
                'User-Agent': jd_ua,
                'Host': 'plogin.m.jd.com'
            }
        });
        praseSetCookies(response)
    } catch (error) {
        cookies = "";
        console.log(error.response.body);
    }
};

/**
 * 获取二维码链接
 */
async function step2() {
    try {
        if (cookies == "") {
            return 0
        }
        let timeStamp = (new Date()).getTime()
        let url = 'https://plogin.m.jd.com/cgi-bin/m/tmauthreflogurl?s_token=' + s_token + '&v=' + timeStamp + '&remember=true'
        const response = await got.post(url, {
            responseType: 'json',
            json: {
                'lang': 'chs',
                'appid': 300,
                'returnurl': 'https://wqlogin2.jd.com/passport/LoginRedirect?state=' + timeStamp + '&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action',
                'source': 'wq_passport'
            },
            headers: {
                'Connection': 'Keep-Alive',
                'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8',
                'Accept': 'application/json, text/plain, */*',
                'Cookie': cookies,
                'Referer': 'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=' + timeStamp + '&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport',
                'User-Agent': jd_ua,
                'Host': 'plogin.m.jd.com',
            }
        });
        token = response.body.token
        okl_token = response.headers['set-cookie'][0]
        okl_token = okl_token.substring(okl_token.indexOf("=") + 1, okl_token.indexOf(";"))
        var qrUrl = 'https://plogin.m.jd.com/cgi-bin/m/tmauth?appid=300&client_type=m&token=' + token;
        return qrUrl;
    } catch (error) {
        console.log(error.response.body);
        return 0
    }
}

/**
 * 获取登录口令
 */
async function step3(qr_url) {
    try {
        let timeStamp = (new Date()).getTime()
        let getCodeUrlObj = new URL(
            'https://api.m.jd.com/api?functionId=jCommand&appid=u&client=apple&clientVersion=8.3.6'
        );
        getCodeUrlObj.searchParams.set(
            'body',
            JSON.stringify({
              appCode: 'jApp',
              command: {
                keyEndTime: timeStamp + 3 * 60 * 1000,
                keyTitle: '【口令登录】点击->立即查看去登录',
                url: qr_url,
                keyChannel: 'Wxfriends',
                keyId: ramdomString(28),
                sourceCode: 'jUnion',
                keyImg:
                    'https://img14.360buyimg.com/imagetools/jfs/t1/188781/6/3393/253109/60a53002E2cd2ea37/17eabc4b8272021b.jpg',
                keyContent: '',
                acrossClient: '0',
              },
            })
        );
        const response = await got.get(getCodeUrlObj.toString(), {
          responseType: 'json',
          headers: {
            Host: 'api.m.jd.com',
            accept: '*/*',
            'accept-language': 'zh-cn',
            'User-Agent': jd_ua,
          },
        });
        return response.body;
    } catch (error) {
        console.log(error.response.body);
        return 0
    }
}

/**
 * 检查 APP 是否已经授权登录
 */
async function checkLogin() {
    try {
        if (cookies == "") {
            return 0
        }
        let timeStamp = (new Date()).getTime()
        let url = 'https://plogin.m.jd.com/cgi-bin/m/tmauthchecktoken?&token=' + token + '&ou_state=0&okl_token=' + okl_token;
        const response = await got.post(url, {
            responseType: 'json',
            form: {
                lang: 'chs',
                appid: 300,
                returnurl: 'https://wqlogin2.jd.com/passport/LoginRedirect?state=1100399130787&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action',
                source: 'wq_passport'
            },
            headers: {
                'Referer': 'https://plogin.m.jd.com/login/login?appid=300&returnurl=https://wqlogin2.jd.com/passport/LoginRedirect?state=' + timeStamp + '&returnurl=//home.m.jd.com/myJd/newhome.action?sceneval=2&ufc=&/myJd/home.action&source=wq_passport',
                'Cookie': cookies,
                'Connection': 'Keep-Alive',
                'Content-Type': 'application/x-www-form-urlencoded; Charset=UTF-8',
                'Accept': 'application/json, text/plain, */*',
                'User-Agent': jd_ua,
            }
        });
        return response;
    } catch (error) {
        console.log(error.response.body);
        let res = {}
        res.body = { check_ip: 0, errcode: 222, message: '出错' }
        res.headers = {}
        return res;
    }
}

/**
 * 检查 config/bak/ 备份目录是否存在，不存在则创建
 */
function mkdirConfigBakDir() {
    if (!fs.existsSync(confBakDir)) {
        fs.mkdirSync(confBakDir);
    }
}

/**
 * 备份 config.sh 文件
 */
function bakConfFile(file) {
    mkdirConfigBakDir();
    let date = new Date();
    let bakConfFile = confBakDir + file + '_' + date.getFullYear() + '-' + date.getMonth() + '-' + date.getDay() + '-' + date.getHours() + '-' + date.getMinutes() + '-' + date.getMilliseconds();
    let oldConfContent = "";
    switch (file) {
        case "config.sh":
            oldConfContent = getFileContentByName(confFile);
            fs.writeFileSync(bakConfFile, oldConfContent);
            break;
        case "crontab.list":
            oldConfContent = getFileContentByName(crontabFile);
            fs.writeFileSync(bakConfFile, oldConfContent);
            break;
        case "sharecode.sh":
            oldConfContent = getFileContentByName(shareCodeFile);
            fs.writeFileSync(bakConfFile, oldConfContent);
            break;
        case "diy.sh":
            oldConfContent = getFileContentByName(diyFile);
            fs.writeFileSync(bakConfFile, oldConfContent);
            break;
        case "task_finish.sh":
            oldConfContent = getFileContentByName(taskFinishFile);
            fs.writeFileSync(bakConfFile, oldConfContent);
            break;
        case "bot.json":
            oldConfContent = getFileContentByName(botFile);
            fs.writeFileSync(bakConfFile, oldConfContent);
            break;
        case "shortcut.list":
            oldConfContent = getFileContentByName(shortCutFile);
            fs.writeFileSync(bakConfFile, oldConfContent);
            break;
        default:
            break;
    }
}

/**
 * 将 post 提交内容写入 config.sh 文件（同时备份旧的 config.sh 文件到 bak 目录）
 * @param content
 */
function saveNewConf(file, content) {
    bakConfFile(file);
    switch (file) {
        case "config.sh":
            fs.writeFileSync(confFile, content);
            break;
        case "crontab.list":
            fs.writeFileSync(crontabFile, content);
            execSync('crontab ' + crontabFile);
            break;
        case "sharecode.sh":
            fs.writeFileSync(shareCodeFile, content);
            break;
        case "diy.sh":
            fs.writeFileSync(diyFile, content);
            break;
        case "task_finish.sh":
            fs.writeFileSync(taskFinishFile, content);
            break;
        case "bot.json":
            fs.writeFileSync(botFile, content);
            break;
        case "shortcut.list":
            fs.writeFileSync(shortCutFile, content);
            break;
        default:
            break;
    }
}

/**
 * 获取文件内容
 * @param fileName 文件路径
 * @returns {string}
 */
function getFileContentByName(fileName) {
    if (fs.existsSync(fileName)) {
        return fs.readFileSync(fileName, 'utf8');
    }
    return '';
}

/**
 * 获取目录中最后修改的文件的路径
 * @param dir 目录路径
 * @returns {string} 最新文件路径
 */
function getLastModifyFilePath(dir) {
    var filePath = '';
    if (fs.existsSync(dir)) {
        var lastmtime = 0;
        var arr = fs.readdirSync(dir);
        arr.forEach(function (item) {
            var fullpath = path.join(dir, item);
            var stats = fs.statSync(fullpath);
            if (stats.isFile()) {
                if (stats.mtimeMs >= lastmtime) {
                    filePath = fullpath;
                }
            }
        });
    }
    return filePath;
}


var app = express();

function shouldCompress(req, res) {
    if (req.headers['x-no-compression']) {
        // don't compress responses with this request header
        return false;
    }
    // fallback to standard filter function
    return compression.filter(req, res);
}

// gzip压缩
app.use(compression({ level: 6, filter: shouldCompress }));

app.use(session({
    secret: 'secret',
    name: `connect.${Math.random()}`,
    resave: true,
    saveUninitialized: true
}));
app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
app.use(express.static(path.join(__dirname, 'public')));


/**
 * 登录 页面
 */
app.get('/', function (request, response) {
    if (request.session.loggedin) {
        response.redirect('./home');
    } else {
        response.sendFile(path.join(__dirname + '/public/auth.html'));
    }
});

/**
 * 首页（配置设定） 页面
 */
 app.get('/home', function (request, response) {
    if (request.session.loggedin) {
        response.sendFile(path.join(__dirname + '/public/home.html'));
    } else {
        response.redirect('/');
    }
});

/**
 * 定时任务 页面
 */
app.get('/crontab', function (request, response) {
    if (request.session.loggedin) {
        response.sendFile(path.join(__dirname + '/public/crontab.html'));
    } else {
        response.redirect('/');
    }
});

/**
 * 互助规则 页面
 */
app.get('/sharecode', function (request, response) {
    if (request.session.loggedin) {
        response.sendFile(path.join(__dirname + '/public/sharecode.html'));
    } else {
        response.redirect('/');
    }
});

/**
 * jup拓展 页面
 */
app.get('/diy', function (request, response) {
    if (request.session.loggedin) {
        response.sendFile(path.join(__dirname + '/public/diy.html'));
    } else {
        response.redirect('/');
    }
});

/**
 * jtask拓展 页面
 */
 app.get('/taskfinish', function (request, response) {
    if (request.session.loggedin) {
        response.sendFile(path.join(__dirname + '/public/taskfinish.html'));
    } else {
        response.redirect('/');
    }
});

/**
 * bot配置 页面
 */
 app.get('/bot', function (request, response) {
    if (request.session.loggedin) {
        response.sendFile(path.join(__dirname + '/public/bot.html'));
    } else {
        response.redirect('/');
    }
});

/**
 * bot快捷功能 页面
 */
 app.get('/shortcut', function (request, response) {
    if (request.session.loggedin) {
        response.sendFile(path.join(__dirname + '/public/shortcut.html'));
    } else {
        response.redirect('/');
    }
});

/**
 * 日志查询 页面
 */
 app.get('/log', function (request, response) {
    if (request.session.loggedin) {
        response.sendFile(path.join(__dirname + '/public/tasklog.html'));
    } else {
        response.redirect('/');
    }
});

/**
 * 修改密码 页面
 */
app.get('/changepwd', function (request, response) {
    if (request.session.loggedin) {
        response.sendFile(path.join(__dirname + '/public/pwd.html'));
    } else {
        response.redirect('/');
    }
});


/**
 * 登录认证 API
 */
 app.post('/api/auth', function (request, response) {
    let username = request.body.username;
    let password = request.body.password;
    fs.readFile(authConfigFile, 'utf8', function (err, data) {
        if (err) console.log(err);
        var con = JSON.parse(data);
        if (username && password) {
            if (username == con.user && password == con.password) {
                request.session.loggedin = true;
                request.session.username = username;
                response.send({ err: 0 });
            } else {
                response.send({ err: 1, msg: authError });
            }
        } else {
            response.send({ err: 1, msg: "请输入用户名密码!" });
        }
    });
});

/**
 * 退出登录 API
 */
 app.get('/api/logout', function (request, response) {
    request.session.destroy()
    response.redirect('/');
});

/**
 * 获取二维码链接 API
 */
app.get('/api/qrcode', function (request, response) {
    if (request.session.loggedin) {
        (async () => {
            try {
                await step1();
                const qr_url = await step2();
                if (qr_url != 0 && qr_url != "") {
                    response.send({ err: 0, qrcode: qr_url });
                } else {
                    response.send({ err: 1, msg: "错误" });
                }
            } catch (err) {
                response.send({ err: 1, msg: err });
            }
        })();
    } else {
        response.send({ err: 1, msg: loginFaild });
    }
})

/**
 * 获取登录口令 API
 */
app.get('/api/jdtoken', function (request, response) {
    if (request.session.loggedin) {
        (async () => {
            try {
                await step1();
                const qr_url = await step2();
                if (qr_url != 0 && qr_url != "") {
                    const jd_token = await step3(qr_url);
                    if (jd_token != 0 && jd_token != "") {
                        response.send({ err: 0, jdtoken: jd_token.data, });
                    } else {
                        response.send({ err: 1, msg: "错误" });
                    }
                } else {
                    response.send({ err: 1, msg: "错误" });
                }
            } catch (err) {
                response.send({ err: 1, msg: err });
            }
        })();
    } else {
        response.send({ err: 1, msg: loginFaild });
    }
})

/**
 * 获取返回的 Cookie 信息 API
 */
app.get('/api/cookie', function (request, response) {
    if (request.session.loggedin && cookies != "") {
        (async () => {
            try {
                const cookie = await checkLogin();
                if (cookie.body.errcode == 0) {
                    let ucookie = getCookie(cookie);
                    response.send({ err: 0, cookie: ucookie });
                } else {
                    response.send({ err: cookie.body.errcode, msg: cookie.body.message });
                }
            } catch (err) {
                response.send({ err: 1, msg: err });
            }
        })();
    } else {
        response.send({ err: 1, msg: loginFaild });
    }
})

/**
 * 获取各种配置文件 API
 */
app.get('/api/config/:key', function (request, response) {
    if (request.session.loggedin) {
        if (configString.indexOf(request.params.key) > -1) {
            switch (request.params.key) {
                case 'config':
                    content = getFileContentByName(confFile);
                    break;
                case 'crontab':
                    content = getFileContentByName(crontabFile);
                    break;
                case 'sharecode':
                    content = getFileContentByName(shareCodeFile);
                    break;
                case 'diy':
                    content = getFileContentByName(diyFile);
                    break;
                case 'taskfinish':
                    content = getFileContentByName(taskFinishFile);
                    break;
                case 'bot':
                    content = getFileContentByName(botFile);
                    break;
                case 'shortcut':
                    content = getFileContentByName(shortCutFile);
                    break;
                default:
                    break;
            }
            response.setHeader("Content-Type", "text/plain");
            response.send(content);
        } else {
            response.send("no config");
        }
    } else {
        response.send(loginFaild);
    }
})

/**
 * 保存文件 API
 */
app.post('/api/save', function (request, response) {
    if (request.session.loggedin) {
        let postContent = request.body.content;
        let postfile = request.body.name;
        saveNewConf(postfile, postContent);
        response.send({ err: 0, title: "保存成功! ", msg: " " });
    } else {
        response.send({ err: 1, title: "保存失败! ", msg: loginFaild });
    }
});

/**
 * 日志列表 API
 */
app.get('/api/logs', function (request, response) {
    if (request.session.loggedin) {
        var fileList = fs.readdirSync(logPath, 'utf-8');
        var dirs = [];
        var rootFiles = [];
        for (var i = 0; i < fileList.length; i++) {
            var stat = fs.lstatSync(logPath + fileList[i]);
            // 是目录，需要继续
            if (stat.isDirectory()) {
                var fileListTmp = fs.readdirSync(logPath + '/' + fileList[i], 'utf-8');
                fileListTmp.reverse();
                var dirMap = {
                    dirName: fileList[i],
                    files: fileListTmp
                }
                dirs.push(dirMap);
            } else {
                rootFiles.push(fileList[i]);
            }
        }
        dirs.push({
            dirName: '@',
            files: rootFiles
        });
        var result = { dirs };
        response.send(result);
    } else {
        response.redirect('/');
    }
});

/**
 * 日志文件 API
 */
app.get('/api/logs/:dir/:file', function (request, response) {
    if (request.session.loggedin) {
        let filePath;
        if (request.params.dir === '@') {
            filePath = logPath + request.params.file;
        } else {
            filePath = logPath + request.params.dir + '/' + request.params.file;
        }
        var content = getFileContentByName(filePath);
        response.setHeader("Content-Type", "text/plain");
        response.send(content);
    } else {
        response.redirect('/');
    }
});

/**
 * 修改密码 API
 */
 app.post('/api/changepass', function (request, response) {
    if (request.session.loggedin) {
        let username = request.body.username;
        let password = request.body.password;
        let config = {
            user: username,
            password: password
        }
        if (username && password) {
            fs.writeFile(authConfigFile, JSON.stringify(config), function (err) {
                if (err) {
                    response.send({ err: 1, msg: "更新出错请重试!" });
                } else {
                    response.send({ err: 0, msg: "更新成功!" });
                }
            });
        } else {
            response.send({ err: 1, msg: "请输入用户名密码!" });
        }
    } else {
        response.send(loginFaild);
    }
});

/**
 * 返回空数据 API
 */
app.get('/api/null', function (request, response) {
    let content = {
            "code": 200,
            "message": "",
            "data": [],
            "powered by": "Task"
    };
    response.setHeader("Content-Type", "application/json");
    response.send(content);
});


app.listen(5678, () => {
    console.log('面板启动，正在监听 5678 端口!');
});
